#include "CPUFlash.h"

uint32_t flash_addr_up_limit = CPU_FLASH_END_ADDR; //默认可写的上限为FLASH的位置

/**
* @brief : CpuFlashErr CPUFlash_UnlockLimit(uint32_t addr_up_limit)
* @param : addr_up_limit : Flash可写的上限地址
* @attention : 用于解锁Flash读写函数的上限地址，如Bootloader写APP的时候
* @author : Sheep
* @date : 24/10/21
*/
CpuFlashErr CPUFlash_ChangeUpLimitAddr(uint32_t addr_up_limit)
{
    flash_addr_up_limit = addr_up_limit;
    return CFE_OK;
}


/**
* @brief : uint8_t CPUFlash_GetPage(uint32_t address)
* @param : address : Flash address,根据地址计算所在的页的首地址
* @attention : 该地址所处的页的首地址
* @return : @ref CpuFlashErr
* @author : Sheep
* @date : 24/10/17
*/
CpuFlashErr CPUFlash_GetPage(uint32_t address, uint32_t* page_addr)
{
	if((address > flash_addr_up_limit ) ||
        (address < CPU_FLASH_START_ADDR))
        return CFE_PARAM_ERR;

    *page_addr = CPU_FLASH_BASE_ADDR+(address - CPU_FLASH_BASE_ADDR)/CPU_FLASH_PAGE_SIZE*CPU_FLASH_PAGE_SIZE;
    return CFE_OK;
}

/**
* @brief : CpuFlashErr CPUFlash_Read(uint32_t flash_addr, uint8_t * pdst, uint32_t size)
* @param : flash_addr : Flash地址, 读取的起始地址
* @param : pdst : 取出的buff的指针
* @param : size : 读取的大小
* @return : @ref CpuFlashErr
* @author : Sheep
* @date : 24/10/18
*/
CpuFlashErr CPUFlash_Read(uint32_t flash_addr, uint8_t * pdst, uint32_t size)
{
    if(((flash_addr + size) > flash_addr_up_limit) ||
        (flash_addr < CPU_FLASH_START_ADDR))
        return CFE_PARAM_ERR;
    if(size == 0)
        return CFE_OK; //size = 0 不拷贝直接返回
    memcpy(pdst, (uint8_t *)flash_addr, size);
    return CFE_OK;
}

/**
* @brief : static CpuFlashErr CPUFlash_cmp(uint32_t addr, uint8_t * pbuff, uint32_t size)
* @param : addr : Flash地址
* @param : pbuff : 要比较的数据的地址
* @param : size : 要比较的大小
* @return : @ref CpuFlashErr
* @author : Sheep
* @date : 24/10/18
*/
static CpuFlashErr CPUFlash_cmp(uint32_t addr, uint8_t * pbuff, uint32_t size)
{
    uint8_t uc_is_equ = 1; //1:相等，0：不相等
    uint8_t uc_byte = 0;  //当前flash的字节内容
    if(((addr + size) > flash_addr_up_limit) ||
        (addr < CPU_FLASH_START_ADDR))
        return CFE_PARAM_ERR;
    if(size == 0) //比较0个字节认为相等
        return CFE_IS_EQU;
    uc_is_equ = 1 ; 
    /* 先假设所有字节和待写入的数据相等，如果遇到任何一个不相等，则设置为 0 */
    for(uint32_t i = 0; i < size; i++)
    {
        uc_byte = *(uint8_t*)(addr + i);
        if(uc_byte != pbuff[i])
        {
            uc_is_equ = 0;/* 不相等，需要写 */
            break;
        }
    }
    if(uc_is_equ == 1)return CFE_IS_EQU;
    else 
    { //如果需要写判断需不需要先擦再写
        for(uint32_t i = 0; i < size; i++)
        {
            uc_byte = *(uint8_t*)(addr + i);
            if(uc_byte != 0xFF)
            {
                return CFE_REQ_ERASE; //只要有一个不是0xFF，则需要擦除 */
            }
        }
        return CFE_REQ_WRITE;   /* Flash不需要擦除，直接写 */
    }
}

/**
* @brief : static CpuFlashErr CPUFLASH_Erase(uint32_t page_addr, uint32_t* err_code)
* @param : page_addr : 要擦除的页首地址(必须保证是页首地址，这个函数也只为内部调用)
* @param : err_code : 错误码(Hal库的参数，返回0xFFFFFFFF表示擦除成功)
* @return : @ref CpuFlashErr
* @author : Sheep
* @date : 24/10/18
*/
static CpuFlashErr CPUFLASH_Erase(uint32_t page_addr, uint32_t* err_code)
{
    HAL_StatusTypeDef status = HAL_OK;
    FLASH_EraseInitTypeDef EraseInitStruct;
    HAL_FLASH_Unlock(); //解锁flash
    EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
    EraseInitStruct.PageAddress = page_addr;
    EraseInitStruct.NbPages = 1;
    EraseInitStruct.Banks = FLASH_BANK_1;
    status = HAL_FLASHEx_Erase(&EraseInitStruct, err_code);
    HAL_FLASH_Lock(); //重新上锁
    if(status != HAL_OK)
        return CFE_FLASH_ERR;
    else return CFE_OK;
}
/**
* @brief : CpuFlashErr CPUFlash_Write_in_A_Page(uint32_t addr, uint8_t * psrc, uint32_t size)
* @param : addr : Flash地址,写入的起始地址
* @param : psrc : 要写入的数据的地址
* @param : size : 要写入的大小(这个函数也只为内部调用，外部调用需注意写入的大小只能在一页之内)
* @return : @ref CpuFlashErr
* @author : Sheep
* @date : 24/10/18
*/
CpuFlashErr CPUFlash_Write_in_A_Page(uint32_t addr, uint8_t * psrc, uint32_t size)
{
    //检查是否需要关中断
    HAL_FLASH_Unlock();
    for(uint32_t i = 0; i < (size/4); i++) //每次按4字节对齐写入
    {
        uint32_t data = *(uint32_t*)(psrc);
        psrc += 4; //移到下一个字
        if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, data) == HAL_OK)
            { addr += 4; }
        else 
        {
            HAL_FLASH_Lock();
            return CFE_FLASH_ERR;
        }
    }
    if(size % 4)
    {
        uint8_t temp[4] = {0};
        memcpy(temp, psrc, size % 4);
        if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, *((uint32_t*)temp)) == HAL_OK)
        {
            HAL_FLASH_Lock();
            return CFE_OK;
        }    
        else 
        {
            HAL_FLASH_Lock();
            return CFE_FLASH_ERR;
        }
    }
    else  //刚好整除的话
    {
        HAL_FLASH_Lock();
        return CFE_OK;
    }
    //检查是否需要开中断
}

/**
* @brief : CpuFlashErr CPUFLash_LoadCode(uint32_t flash_addr, uint8_t * psrc, uint32_t size)
* @param : 
* @attention : 这个函数只是用于装载代码，该函数不是随机读写函数，随机读写函数在下方，
                只适用于loadcode这样只会不断往后写的场景(相比于随机读写，优势在于，不用数组缓存，省内存)
                loadcode时，可以多段装载，但首次装载的地址必须是页首地址。
* @author : Sheep
* @date : 24/11/07
*/
CpuFlashErr CPUFLash_LoadCode(uint32_t flash_addr, uint8_t * psrc, uint32_t size)
{
    CpuFlashErr ret = CFE_OK;
    uint32_t write_num_once = 0; //每次要写入的数据量
    // ret = CPUFlash_cmp(flash_addr,psrc,size); //对全文检查是否相等
    // if(ret == CFE_IS_EQU) return CFE_OK;// 全文相等，不需要擦写
    if(flash_addr % CPU_FLASH_PAGE_SIZE == 0)ret = CFE_REQ_ERASE; //传入的是每页页首的话都需要擦除
    while(size) //没有写入完成
    {
        if(size <= CPU_FLASH_PAGE_SIZE) //写入的数据不超过这一页
            write_num_once = size;  
        else  //写入的数据超过这一页,先写入到这页尾的数量
            write_num_once = CPU_FLASH_PAGE_SIZE;
        if(ret == CFE_REQ_ERASE) //需要擦除
        {
            uint32_t err_code = 0;
            CpuFlashErr temp = CFE_OK;
            temp = CPUFLASH_Erase(flash_addr, &err_code);//先擦除这一整页
            if(temp != CFE_OK || err_code!= 0xFFFFFFFF)return CFE_FLASH_ERR; //hal返回错误
        }
        CpuFlashErr temp = CPUFlash_Write_in_A_Page(flash_addr, psrc, write_num_once);
        if(temp != CFE_OK)return temp;
        psrc += write_num_once;
        flash_addr += write_num_once;
        size -= write_num_once;
    }
    return CFE_OK;
}

#if CPU_FLASH_RANDOM_WRITE_ENABLE
uint8_t flash_temp_buff[CPU_FLASH_PAGE_SIZE] = {0}; 
/**
* @brief : CpuFlashErr CPUFlash_Write(uint32_t flash_addr, uint8_t * psrc, uint32_t size)
* @param : flash_addr : Flash地址,写入的起始地址
* @param : psrc : 要写入的数据的地址
* @param : size : 要写入的大小
* @return : @ref CpuFlashErr
* @attention : 这个函数支持多页写入，局部写入，都可以。
* @author : Sheep
* @date : 24/10/18
*/
CpuFlashErr CPUFlash_Write(uint32_t flash_addr, uint8_t * psrc, uint32_t size)
{
    CpuFlashErr ret = CFE_OK;
    uint32_t flash_page_addr = 0; //要写入页的起始地址
    uint32_t write_num_once = 0; //每次要写入的数据量
    while(size) //没有写入完成
    {
        ret = CPUFlash_GetPage(flash_addr,&flash_page_addr);
        if(ret != CFE_OK) return ret; //地址超出范围返回参数错误
        if(size <= (flash_page_addr+CPU_FLASH_PAGE_SIZE - flash_addr)) //写入的数据不超过这一页
            write_num_once = size;  
        else  //写入的数据超过这一页,先写入到这页尾的数量
            write_num_once = flash_page_addr+CPU_FLASH_PAGE_SIZE - flash_addr;
        ret = CPUFlash_cmp(flash_addr, psrc, write_num_once); //对比要写入的数据是否相等
        switch (ret)
        {
            case CFE_IS_EQU://如果写入的数据和falsh相等，则不需要写入
                break;
            case CFE_REQ_ERASE: //需要先擦除
                {
                    uint32_t err_code = 0;
                    CpuFlashErr temp = CFE_OK;
                    CPUFlash_Read(flash_page_addr, flash_temp_buff, CPU_FLASH_PAGE_SIZE);//先读出这一整页
                    temp = CPUFLASH_Erase(flash_page_addr, &err_code);//再擦除这一整页
                    if(temp != CFE_OK || err_code!= 0xFFFFFFFF)return CFE_FLASH_ERR; //hal返回错误
                    memcpy(&(flash_temp_buff[flash_addr-flash_page_addr]), psrc, write_num_once); //将数据复写到读出的缓存中
                    temp = CPUFlash_Write_in_A_Page(flash_page_addr, flash_temp_buff, CPU_FLASH_PAGE_SIZE); //把整页写回
                    if(temp != CFE_OK)return temp;
                }
                break;
            case CFE_REQ_WRITE: //不用擦除直接写
                {
                    CpuFlashErr temp = CPUFlash_Write_in_A_Page(flash_addr, psrc, write_num_once);
                    if(temp != CFE_OK)return temp;
                }
                break;
            default: break;
        }
        size -= write_num_once; 
        flash_addr += write_num_once;
        psrc += write_num_once;//写入后，将指针全部迁移
    }
    return CFE_OK;
}

#endif
